Repository navigation
fix(lint)!: os build and the object_extension publish door judge each validation rule an object extension adds by the object's own rule pass - #33205
Conversation
…extension adds, by the object walk's own rule pass WIP: pins, changeset and migration entry follow. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibyToDkPxCjvNiWsv6e
… the object_extension gate and the real publish door Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibyToDkPxCjvNiWsv6e
…ild and the object_extension door Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibyToDkPxCjvNiWsv6e
…s a rule declared on the object must Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibyToDkPxCjvNiWsv6e
…; the extension rule loop adds no receiver Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibyToDkPxCjvNiWsv6e
…its nullable operand Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibyToDkPxCjvNiWsv6e
…now meets, and its fix Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibyToDkPxCjvNiWsv6e
…-mn9za6 # Conflicts: # packages/lint/src/authored-site-expression.test.ts
📓 Docs Drift CheckThis PR changes 2 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 172 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d6c1cf20d4112e29732a134f1516f35025bf7d15 && git checkout d6c1cf20d4112e29732a134f1516f35025bf7d15
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6e1ab9b286655c5476312ae563ab531f3f36cf38 9d07ffa069baf93055b4da43e19fccbf9237159c && git checkout -B drift-repro 6e1ab9b286655c5476312ae563ab531f3f36cf38 && git merge --no-ff 9d07ffa069baf93055b4da43e19fccbf9237159c
node scripts/docs-audit/affected-docs.mjs --json 6e1ab9b286655c5476312ae563ab531f3f36cf38
|
|
domain:spec#2 · os-tesla · session_01L9qibvfX5bawZKtfa6u7j9 Contract-review-claim: session_01L9qibvfX5bawZKtfa6u7j9 · head 15d0747 · 2026-11-14T03:07Z Face: schema + narrowing ( Generated by Claude Code |
|
domain:spec#2 · os-tesla · session_01L9qibvfX5bawZKtfa6u7j9 Contract reviewServed-tier: PR #33205 for card #33154, on the schema face (the semantic entry) and the declared narrowing. Read: the card body and its five comments (6865785642, 6868572380, 6868654518, 6869487624, 6870691534), the PR body and its nine-path file list, the net diff against One must-fix (F1), one should-fix (F2), three nits (N1 to N3). ① Derived judgments
② Semver levelThe changeset moves The ③ Boundary flags
④ Design judgmentWhat was tried against the design, and held:
The hoist is faithful (① item 7). The extension loop passes the target, the entry locator and the rule's key path, and the return after the walk is intact. Two findings on the contract, two nits. F1, must-fix. At the real
F2, should-fix. A one-hop read through a reference field an extension adds has its leaf unjudged, so the card's trap stays open in that corner.
N1, nit. The key path's index is a counter over N2, nit. The comment at N3, nit, in the
Fix both with F1's edit to the entry, or leave them as accepted deviations.
Comment only, as amended. Its measured sentence holds on reading: The four test files (implementing)
Outside the diff, read only: the Implemented-by: VERDICT: FAIL Generated by Claude Code |
…columns The object_extension door hands the objects as the registry serves them, with the injected system columns (created_at, owner_id, ...) declared, so the null-guard gate judged record.created_at as a declared nullable field and refused an extension rule os build accepts. On that write each object is now read through stripInjectedSystemColumns, as the authored-site expression judge does. Pinned at the gate and the real door (accepted at both; a declared nullable field still refused at both). Comments, the changeset and the semantic entry now say that the leaf of a one-hop read through an extension-added reference is not judged, that the door's 422 keeps the stack path, that os lint narrows too, and that the null-guard gate judges ordering and arithmetic operands only. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibyToDkPxCjvNiWsv6e
|
domain:spec#2 · os-tesla · session_01L9qibvfX5bawZKtfa6u7j9 Contract-review-hold: session_01L9qibvfX5bawZKtfa6u7j9 · head 9d07ffa · the maintainer paused lane seats' own contract reviews; reviews go to the new review agent · until a review agent is seated and takes this head Handover · PR #33205 (card #33154) · 2026-11-14T04:39ZThe seat signs off by the maintainer's instruction. The PR stays a draft and is not accepted. Nothing is armed.
Generated by Claude Code |
|
domain:spec#2 · os-tesla · session_01L9qibyToDkPxCjvNiWsv6e Contract-review-claim: session_01L9qibyToDkPxCjvNiWsv6e · head 9d07ffa · 2026-11-14T14:19Z Face: schema + narrowing ( Generated by Claude Code |
|
domain:spec#2 · os-tesla · session_01L9qibyToDkPxCjvNiWsv6e Re-run, once: Generated by Claude Code |
|
domain:spec#2 · os-tesla · session_01L9qibyToDkPxCjvNiWsv6e The brief handed to the isolated reviewer at Contract review brief — PR #33205 @
|
|
domain:spec#2 · os-tesla · session_01L9qibyToDkPxCjvNiWsv6e Contract reviewServed-tier: PR #33205 for card #33154, on the schema face (the semantic entry) and the declared narrowing. This is a full review of the rework head. It also judges, finding by finding, whether F1, F2, N2 and N3 of the earlier record 6870963898 (head Read: the card body and its six comments; the PR body, its nine-path file list and its six comments (the earlier record, the hand-over 6871853372, the two claims and the re-run note among them); the net diff against No must-fix and no should-fix. Four nits (N4 to N7). One follow-up card is owed (the flow and hook doors, under ③). Check-runs at 2026-11-14T14:28Z: 71 runs: 58 success, 12 skipped, 1 in progress. The one without a conclusion is ① Derived judgments
② Semver levelThe changeset moves
③ Boundary flags
④ Design judgmentThe earlier record's findings, judged in the tree at this head.
Read sentence by sentence against the tree.
The code agrees with the contract. The hoist is faithful (① item 7). The extension loop ( Tried against the strip, and held:
N4, nit. N5, nit, in the N6, nit, prose face (changeset). The Now paragraph names one effect of the strip: N7, nit, prose face (PR body). The PR body's
Comment only, as amended. Its measured sentence holds on reading: The four test files (implementing)
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:spec#2 · os-tesla · session_01L9qibyToDkPxCjvNiWsv6e 维护者速读 · PR #33205 @
|
|
domain:spec#2 · sunhaolin · session_017xRBcSbLCxEmyHf9upz3pz 维护者速读 · PR #33205 @
|
Requested by Tesla · project thread
Fixes #33154
Clause-②: no (narrowing) —
os build,os validate,os lintand the runtimeobject_extensionpublish door (Studio,PUT /api/v1/meta/object_extension/TARGET, the MCP and AI authoring doors) now refuse an extension whose validation rule carries aconditionorwhenthat the same rule declared on the object is refused for. No exported type or signature changes. Measured first-party exposure: 0 extension entries carry a validation rule (examples/app-showcaseandpackages/plugins/plugin-authextensions add fields only; HotCRMorigin/main92519d8 has noobjectExtensionsin code), so nothing first-party is newly refused. Two repository test fixtures carried the refused shape and are repaired here (see Acceptance notes).Metadata-protocol: narrowed
Skills-docs: none owed — this PR edits
content/docs/deployment/validating-metadata.mdx(the object extension door paragraph names the rule pass);skills/objectstack-datateaches validation rules anddefineObjectExtension({ extend, fields })without saying an extension's rules are exempt, so the build and the door now give the verdict the object's own rules already get.Precedent followed: #33072 (PR #33119), which declared the same
no (narrowing)/narrowedpair for the computed fields an extension adds.维护者速读
objectExtensions/ Studio 发布的object_extension)里的validations校验规则,现在在os build和运行时发布口都按对象自身校验规则的同一套检查判定(同一个函数judgeValidationRule、同一条规则 idexpression-invalid),位置为object '对象' · objectExtensions[0] · validation '规则'。region__c,一个未加空值守卫的record.qx_credit > 5000000),已在本 PR 修正。草稿保存不受影响,已存的行再次发布时才判。回滚即 revert。packages/lint/src/validate-expressions.ts(judgeValidationRule、扩展遍历里新增的循环、扩展写入时去掉注入系统列的一行)、packages/lint/src/runtime-gate.object-formula-writes.test.ts(An object extension's validations are judged at neither the publish door nor os build, yet the registry registers them on the target object #33154 一节)、packages/runtime/src/domains/meta-object-extension-item.test.ts(真实发布口 4 条)。pnpm --filter @objectstack/lint exec vitest run src/runtime-gate.object-formula-writes.test.ts -t 33154,预期 9 条全过:拼错字段与未知函数被拒、build 与发布口结论逐条相同(含平台注入的系统列created_at:两边都接受)、合法规则与读同一扩展新增字段的规则通过。9d07ffa0),无 must-fix / should-fix,4 个 nit 记为接受的偏差;上一轮 FAIL(6870963898)的 F1、F2、N2、N3 已在树上核实解决。后续卡 A one-hop read through a lookup an object extension adds is accepted with a misspelt target field, then refuses every save #33216、An object extension's format and json_schema validation rules skip the regex and JSON-schema checks the object's own rules get #33217、A flow or hook condition ordering record.created_at with no guard is refused at the publish door while os build accepts it #33246。复审由实现本 PR 的同一会话采纳隔离子代理的结论,依据维护者 2026-11-14 14:17Z「你现在可以自己契约复审了」。非受管面,579 行。What changes
Lint: one rule pass, two walks.
packages/lint/src/validate-expressions.ts.judgeValidationRule(rule, { objectName, locus, rulePath }). It covers theconditionandwhen(with the one-hop checks the rule validator serves), every nestedthen/otherwisepredicate, theparentroot verdict and the null-guard gate. The object walk's loop now calls it, and its findings, order and key paths are byte for byte what they were.validations[]rule of each entry thatobjectExtensionsByTargetkeeps (the fold already carried them). Arecord.FIELDread resolves against the target's fields as served (fieldIndex, overobjectsAsServed), so a field this extension or a sibling package's extension adds resolves, and a field nothing serves is unknown. An undeclared target is judged on what does not depend on its fields, as its formulas are. The hop checks (refuseUndeclaredHopField,storedMetadataBodyReferencesFor) read the declared objects, so the leaf of a one-hop read through a reference an extension adds is not judged (see Acceptance notes).object 'TARGET' · objectExtensions[i] · validation 'RULE', and the key path isENTRY.validations[k].SLOT. The door's 422 carries that path as is (objectExtensions[0].validations[0].condition); only a draft read's_diagnosticsre-spell it item-relative (validations.0.condition).object_extensionwrite the door's live objects are read throughstripInjectedSystemColumns(@objectstack/metadata-core), asauthored-site-expression.tsreads served objects. The registry serves an object with the system columns it injects (created_at,owner_id, …) declared,required: falseand no default, so the null-guard gate judgedrecord.created_at > …as an unguarded nullable operand and refused at the door whatos buildaccepts. Stripped, the indexes re-derive those columns as the build does: known fields, never judged nullable.object_extensionwrite the door still runs ONE pass, the extension walk, and returns after it (lint: the object_extension publish door runs no field-formula judgment, so a computed field os build refuses still publishes through PUT /meta/object_extension #33072'sextensionWrite). The rules join that one pass.packages/lint/src/authoring-rules.ts. Comment only (surface amendment 6868654518), on the expression rule'sruntimeTypes.Docs, migration and changeset.
content/docs/deployment/validating-metadata.mdx. One sentence in the object extension door paragraph. The CEL row's columns do not change: theᵒˣmark landed with lint: the object_extension publish door runs no field-formula judgment, so a computed field os build refuses still publishes through PUT /meta/object_extension #33072.packages/spec/src/migrations/entries/semantic/18.object-extension-validation-rule-expression-judged.ts. The ADR-0087 D3 entry. Its printed guidance carries no tracker id. There is no D2 conversion, because how a refused rule is repaired is a fact only the author holds..changeset/33154-object-extension-validation-rules-judged.md.@objectstack/lintminor and@objectstack/specminor, with both declaration lines, a BREAKING banner (Before / Now / Unchanged / Fix) that names the null-guard refusal and its fix, and the ADR-0087 disposition marker.Tests
packages/lint/src/runtime-gate.object-formula-writes.test.ts, a new top-level describe#33154with 9 cases, at the real gate (runRuntimeAuthoringRules) and the build (runAuthoringRules('build', …)):thenbranch is refused at the branch.sqrt, a bare reference), the door's findings deep-equal the build's.applyInjectedSystemColumns), an unguarded ordering overrecord.created_atis accepted at the door as at the build, and one over a declared nullable field (record.amount > 0) is refused at both, the findings deep-equal.packages[2].manifest.objectExtensions[0].packages/runtime/src/domains/meta-object-extension-item.test.ts, a new describe[#33154]with 4 cases, through the realsaveMetaItem+publishMetaItem:record.nmaegets 422INVALID_METADATAwith issueexpression-invalidat the build's location and path. The rule is not served, and the row stays a draft.record.created_at > timestamp(…)with no guard publishes and is served (the account as served declarescreated_at), andos build(runAuthoringRules('build', …)on the account as authored and the same entry) reports nothing; an unguardedrecord.bonus__c > 100on a field the same entry adds is refused at both, with the same rule, location and path.meta-object-extension-item.test.ts: a bareregion__c == nullbecomesrecord.region__c == null.packages-disable-module-extension-fields.test.ts(surface amendment 6869487624): an unguardedrecord.qx_credit > 5000000gainsrecord.qx_credit != null &&.authored-site-expression.test.ts: thevalidate_expressionenumeration's source regions now read the hoisted rule pass.indexOfandisArraymoved out of the region with the object loop's own index, so they leave the member-call table.Ablation (one-shot; nothing permanent):
node scripts/ablation-replace.mjsreplaced the extension walk'sfor (const rule of rules) {with an empty loop (anchor x1 → x0, blob a3c3164d → 1bba8602). Thenpnpm --filter @objectstack/lint buildran, andablation-dist-preflightfound the marker in 4 built files.dist/: 2 of 3 red. The control stayed green.git diff HEADis empty. Lint was rebuilt, andablation-dist-preflight --absentpasses on all 20 built files with a clean tree.[] as typeof rules. The dist reading passed on that run, and the restore leg passed--source-marker.Ablation of the strip (r1, one-shot):
ablation-replace.mjsreplacedif (extensionWrite) stack = {withif (extensionWrite && String('ablated-strip') === 'off') stack = {(anchor x1 → x0, blob 72951696 → e6b23d5a). Lint was rebuilt, andablation-dist-preflightfound the marker in 4 built files.record.created_at, the build none).422onrecord.created_at).git diff HEADis empty. Lint was rebuilt, andablation-dist-preflight --absentpasses on all 20 built files with a clean tree.Acceptance notes
validate-rule-compilabilitychecks aformatrule's regex.validate-rule-schema-formatschecks ajson_schemarule's formats.obj.validations, so a regex or schema format in an extension's rule is judged at neither door. Neither is an expression pass. The changeset's Unchanged line says so.refuseUndeclaredHopFieldandstoredMetadataBodyReferencesForread the declared objects, so withmanager__c(a lookup to staff) added by the extension,record.manager__c.daly_hours > 12passes the build and the door alike (measured), while the same misspelling through a reference the target declares is refused. The changeset's Unchanged line says so. Filed as A one-hop read through a lookup an object extension adds is accepted with a misspelt target field, then refuses every save #33216.record.created_atin a record-triggered flow or a hook condition is refused at the door whileos buildaccepts it. It is out of this card's scope: those walks are not in this diff. Filed as A flow or hook condition ordering record.created_at with no guard is refused at the publish door while os build accepts it #33246.defineStack({ objectExtensions })throughregisterApp) are not gated, as an object's own rules are not.packages-disable-module-extension-fields.test.tsstill registers unguarded module rules that way, untouched.Verification
Head
9d07ffa06(rework r1 on15d074782: the strip, its pins, and the prose fixes).origin/mainwas merged twice before r1 (1169d64 before opening, then after #33168 landed onauthored-site-expression.test.ts); r1 merges nothing, andgit merge-treeagainstorigin/mainat push time is clean. Everything below ran at9d07ffa06unless it names another head.Package tests
@objectstack/lintat9d07ffa06: 208/208 files, 8879/8879 tests. Typecheck exit 0.9d07ffa06(meta-object-extension-item.test.ts+packages-disable-module-extension-fields.test.ts): 74/74.object_extension, at9d07ffa06: metadata-protocolprotocol-extension-redeclared-owner-field,protocol-stored-item-conversion.notice-name,protocol-authoring-gate.extension-write-fold,protocol.packaged-base-refusal,protocol.unknown-picklist-binding-refusal(129/129); runtimemeta-picklist-binding-door,packages-export-base-showcase-boot.integration,meta-object-extension-item-door-fit(22/22).e4ee8af8e(before r1; r1 changes only the extension door's object read and prose):@objectstack/metadata-protocol22810 tests passed,@objectstack/objectql12874,@objectstack/platform-objects1955,@objectstack/mcp1145,@objectstack/cloud-connection1206,@objectstack/plugin-approvals1632,@objectstack/service-mailbox152,@objectstack/rest8346 (332 skipped),@objectstack/cliunit 5934 and integrationtest/migrate-meta-engine-guidance.test.ts3/3,@objectstack/runtime597 files after the fixture repair.Gates (dispatch-gates --commands, 109 lines)
The derivation at
9d07ffa06is the same 109 lines as before r1. Every line ran with stdin closed and its exit code captured before any pipe. 103 exited 0 on the first pass; six@objectstack/speclines (check:api-surface,check:browser-reachable-entries,check:dual-source-exports,check:entry-nameability,check:exported-any,check:skill-examples) exited 3 (refused, nothing measured) because the spec build predated the edited semantic entry. Afterpnpm --filter @objectstack/spec buildall six exited 0.dispatch-gates --ran:109 derived, 109 run, 0 NOT-MEASURED.Dogfood (the title carries
!), full battery, the CI command, no shard, on head9d07ffa06. The closure was built first. Result:VERDICT command-exit 0, Test Files 463 passed and 3 skipped (466), Tests 3918 passed and 42 skipped (3960), no failures.Generated by Claude Code